from typing import Dict, TypeVar, List, Optional, NewType
from datetime import datetime
from kds_util.user_logbook import user_log as logger
import requests
from dataclasses import dataclass, field
from base.BaseCD import CDBASE5, BSma5, MA, RunDCond002, RunDCond003, ATR, D_MA, MACD
from base.formula import CDBASE20, DoubleMA, MACDDivergence
from base.kBase import KLine, Contract
from base.macdbs import MACD_BS
from enum import Enum
from highlow.envs import Envs

TBreed = TypeVar('TBreed', bound=str)  # 定义TBreed类型
TBkId = TypeVar('TBkId', bound=str)  # 定义TBkId
TCodeL6 = TypeVar('TCodeL6', bound=str)
TContract = NewType('TContract', str)  # 定义合约类型


class ESignalType(Enum):
    NoSignal = 'NoSignal'  # 没有信号
    DdSignal = 'DdSignal'  # 顶底信号


@dataclass
class KlineItem:
    """K线数据定义"""
    time: int = 0
    open: float = 0.0
    high: float = 0.0
    low: float = 0.0
    close: float = 0.0
    volume: float = 0.0
    oi: int = 0
    contract: str = ""
    period_name: str = ""
    index: int = -1
    trade_date: str = ""
    trade_time: str = ""
    dt: Optional[datetime] = None

    def __post_init__(self):
        self.dt = datetime.fromtimestamp(self.time)
        self.trade_date = self.dt.strftime("%Y-%m-%d") if self.dt else ""
        self.trade_time = self.dt.strftime("%Y-%m-%d %H:%M:%S") if self.dt else ""


def load_kline(code, period="1m", limit=240000, s_time="", e_time="") -> List[KlineItem]:
    """通过接口获取k线数据，增加基础计算"""
    param = {
        "user": "100000",
        "token": "luhouxiang",
        "code": code,
        "period": period,
        "s_time": s_time,  # 开始时间可以不给出
        "e_time": e_time,  # 结束时间缺省是当前时间
        "count": limit  # 数量在只有结束时间的时候有效，建议不为0
    }
    headers = {'Content-Type': 'application/json;charset=UTF-8', 'Connection': 'keep-alive'}
    url = f'http://{Envs.dest_ip}:8086/api/hqqh/kline'
    # logger.info(F"url: {url}")
    # logger.info(F"request_kline:param: {json.dumps(param, indent=4)}")
    try:
        r = requests.request('POST', url, json=param, headers=headers)
    except Exception as exception:
        logger.error(str(exception))
        try:
            url = 'http://192.168.1.101:8086/api/hqqh/kline'
            r = requests.request('POST', url, json=param, headers=headers)
        except Exception as exception:
            logger.error(str(exception))
    j = r.json()

    lists = j["result"]['lists'] if j.get('err_code', -1) == 0 else []
    # names = j["result"]['names'] if j.get('err_code', -1) == 0 else []
    klines: list[KlineItem] = [KlineItem(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], period, index) for index, d in
                               enumerate(lists)]
    # logger.info(F"request_kline:status:{r.status_code}, kline_count:[{len(klines)}], post_url:{url}")
    return klines


@dataclass
class HoldItem:
    contract: TContract = TContract("")
    long_position: int = 0  # 多头持仓
    short_position: int = 0  # 空头持仓
    long_open_amount: float = 0.0
    long_close_amount: float = 0.0
    short_open_amount: float = 0.0
    short_close_amount: float = 0.0
    last_price: float = 0.0
    long_open_position: int = 0  # 多头开仓数
    short_open_position: int = 0  # 空头开仓数
    long_profit: float = 0.0
    short_profit: float = 0.0
    long_today_profit: float = 0.0  # 多头当日盈亏
    short_today_profit: float = 0.0  # 空头当日盈亏
    trade_day: int = 0

    @property
    def net_position(self):
        return self.long_position + self.short_position

    @property
    def long_cost(self):
        if self.long_position == 0:
            return 0
        else:
            return (self.long_open_amount - self.long_close_amount) / abs(self.long_position)

    @property
    def short_cost(self):
        if self.short_position == 0:
            return 0
        else:
            return (self.short_open_amount - self.short_close_amount) / abs(self.short_position)


@dataclass
class BreedItem:
    dd_forward_5_day_zdf: float = 0.0  # 顶底为界向前5天的涨跌幅度
    breed_side: int = 0
    born_type: str = ""  # 产生方式：信号 或 持仓
    breed_id: str = ""


@dataclass
class BkItem:
    signal_breed: str = ""  # 最终排序出来确定的有信号的来代表板块的指数breed
    components: Dict[str, BreedItem] = None  # type: ignore
    trade_type: str = ""  # 交易类型， OpenOptPosition，CloseOptPosition，临时清仓， （前四种任意一种发生，不再执行另外的仓位处理）
    bk_code: str = ""
    delegate_from_signal: str = ""

    def __post_init__(self):
        self.components = {}


class DDParam:
    def __init__(self, contract) -> None:
        self.score = 0.0
        self.day_kline = KLine()  # 当日合成的日线，动态的
        self.count1m = 0  # 当天1分钟K线计数
        self.turnover = 0.0
        self.volume = 0
        self.dma5: D_MA = D_MA(5)
        self.ddir = 0
        self.Select = 0
        self.dir = 0
        self.mav = 0.0
        self.contract_info: Contract = contract
        self.Atr: ATR = ATR()  # ATR计算类,用于计算大波动
        self.bs_minute: CDBASE5 = CDBASE5()  # 计算日内金叉死叉
        self.bs_ma5: BSma5 = BSma5()  # 计算模糊ma5多空
        self.jx_flag: bool = False  # 日内金叉标识
        self.sx_flag: bool = False  # 日内死叉标识
        self.zhengshu: bool = False  # 日内碰整数标识
        self.dd_disabled: bool = False  # 黑名
        self.forced_stop: bool = False  # 强制止损标识，3个交易日内被强制止损
        self.hq_status: bool = False
        self.one_minute_atr = ATR()
        self.ma5: MA = MA(5)
        self.jx_sx: CDBASE20 = CDBASE20()   # 其值返回1表示金叉，-1表示死叉
        self.ks5 = []
        self._force_trade: bool = False
        self.MACDDiver: MACDDivergence = MACDDivergence()   # MACD 背离指标，3背离
        self.DbMa: DoubleMA = DoubleMA()    # 双均线，表达20，60均金死叉状态的
        self.MACDBS: MACD_BS = MACD_BS(kline=KlineItem())
        self.m30_macd: MACD = MACD(30)

    def set_hq_status(self, status: bool):
        self.hq_status = status

    @property
    def trade_able(self):
        """当前分钟有行情，就认为可以交易"""
        if self._force_trade:
            return True
        if self.hq_status:
            dg_time = self.contract_info.dt.hour * 100 + self.contract_info.dt.minute
            if self.contract_info.ExchangeId == "CFFEX":    # 中金所
                trading = (945 <= dg_time < 1500)
            else:
                trading = (915 <= dg_time < 1500) or (2115 <= dg_time)
            return trading
        else:
            return False

    def reset_status(self):
        """
        重置部分状态值
        :return:
        """
        pass

    def reset_new_one_day(self):
        self.day_kline = KLine()    # 新的一天，重新初始化

        self.ma5_real_time_time = ""
        self.ma5_real_time_side = 0
        self.jx_flag = False
        self.sx_flag = False
        self.zhengshu = False


class OpenOptItem:
    """
    开仓项，通常一个开仓项包含开仓的相关操作和加仓的相关操作，通常减仓的逻辑可以忽略
    """
    def __init__(self, code, side, price, volume, dt, multiplier):
        self.code: str = code
        self.side: int = side
        self.multiplier: int = multiplier
        self.open_price: float = price
        self.open_volume: int = volume
        self.open_time: datetime = dt
        self.max_profit: float = float("-inf")   # 最近一次开仓到现在的最大盈利
        self.cur_profit: float = 0.0    # 最近一次开仓到现在当前盈利
        self.uneven_volume: int = volume     # 未平仓的量
        self.stop_win_flag: bool = False    # 止赢条件激活回撤30%
        self.market_value: float = -1 * self.open_price * self.open_volume * self.side * self.multiplier

    def close_opt(self, opt_price, change_position, dt, opt_status):
        change_position = abs(change_position)
        self.uneven_volume -= change_position
        self.market_value += (1 * opt_price * change_position * self.side * self.multiplier)
        if self.uneven_volume != 0:
            logger.error(f"{self.code}:清仓量不对,应为0,实际为:{self.uneven_volume},side:{self.side},price:{opt_price}"
                         f"清仓量:{change_position},dt:{str(dt)}")

    def calc_profit(self, price: float):
        close_value = (1 * price * self.uneven_volume * self.side * self.multiplier)
        self.cur_profit = round(self.market_value + close_value, 2)
        if self.cur_profit > self.max_profit:
            self.max_profit = self.cur_profit
        if self.max_profit > 0:
            if self.cur_profit / self.max_profit < 0.7:
                if not self.stop_win_flag:
                    pass
                    # logger.info(f"{self.code}.{self.side}:止赢条件达成:{[self.cur_profit / self.max_profit]}")
                self.stop_win_flag = True
            else:
                self.stop_win_flag = False
        else:
            self.stop_win_flag = True

    def is_rollback(self):
        return self.stop_win_flag

    def __str__(self):
        return f"side:{self.side},p:{self.open_price},v:{self.open_volume},t:{str(self.open_time)}"

    def __repr__(self):
        return self.__str__()


class OpenOptInfo:
    """
    开仓项的集合
    """
    def __init__(self):
        self.opens: Dict[str, OpenOptItem] = {}


def create_new_breed_item(breed: TBreed, breed_side: int, born_type: str = "hold_only"):
    item = BreedItem(breed_side=breed_side, breed_id=breed, born_type=born_type)
    return item


def create_new_bk_item(bk_items, bk_code, born_type="hold_only"):
    bk_items[bk_code] = BkItem()
    bk_items[bk_code].bk_code = bk_code
    bk_items[bk_code].delegate_from_signal = born_type
    return bk_items[bk_code]